package com.li.commons.pager;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageHelper implements Interceptor {
   
	private static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();

    private static final List<ResultMapping> EMPTY_RESULTMAPPING = new ArrayList<ResultMapping>(0);

    private static String dialect = "";
    
    private static boolean offsetAsPageNum = false;
    
    private static boolean rowBoundsWithCount = false;

    public static void startPage(int pageNum, int pageSize) {
        startPage(pageNum, pageSize, true);
    }

    public static void startPage(int pageNum, int pageSize, boolean isCount) {
        localPage.set(new Page<Object>(pageNum, pageSize, isCount));
    }

    public Object intercept(Invocation invocation) throws Throwable {
        final Object[] args = invocation.getArgs();
        RowBounds rowBounds = (RowBounds) args[2];
        //无分页需求
        if (localPage.get() == null && rowBounds == RowBounds.DEFAULT) {
            return invocation.proceed();
        } else {
            args[2] = RowBounds.DEFAULT;
            MappedStatement ms = (MappedStatement) args[0];
            Object parameterObject = args[1];
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            Page page = localPage.get();
            localPage.remove();
            if (page == null) {
                if (offsetAsPageNum) {
                    page = new Page<Object>(rowBounds.getOffset(), rowBounds.getLimit(), 0 ,rowBoundsWithCount);
                } else {
                    page = new Page<Object>();
                }
            }
            MappedStatement qs = newMappedStatement(ms, new BoundSqlSqlSource(boundSql));
            args[0] = qs;
            MetaObject msObject = SystemMetaObject.forObject(qs);
            String sql = (String) msObject.getValue("sqlSource.boundSql.sql");
            if (page.isCount()) {
                msObject.setValue("sqlSource.boundSql.sql", getCountSql(sql));
                Object result = invocation.proceed();
                int totalCount = (Integer) ((List) result).get(0);
                page.setTotal(totalCount);
                int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
                page.setPages(totalPage);
                if(page.getPageNum()>totalPage){
                	page.setPageNum(totalPage);
                }
                msObject.setValue("sqlSource.boundSql.sql", getPageSql(sql, page));
                msObject.setValue("resultMaps", ms.getResultMaps());
                result = invocation.proceed();
                page.addAll((List) result);
                return page;
            } else {
                msObject.setValue("sqlSource.boundSql.sql", getPageSql(sql, page));
                msObject.setValue("resultMaps", ms.getResultMaps());
                Object result = invocation.proceed();
                page.addAll((List) result);
                return page;
                
            }
        }
    }

    private String getCountSql(String sql) {
    	String fromSql = "";
    	String countColumn="*";
    	String countColumnRegex = "^select[\r\n\t\\s]+[\\w]*(distinct[\r\n\t\\s]+[\\.\\w\\d]+)[\r\n\t\\s]*,";
    	Pattern countColumnPattern = Pattern.compile(countColumnRegex);
    	Matcher countColumnMatcher = countColumnPattern.matcher(sql.toLowerCase());
    	if(countColumnMatcher.find()){
    		countColumn = countColumnMatcher.group(1);
    	}
    	countColumn = countColumn.replaceAll("\n"," ").replaceAll("\t", " ").replaceAll("\r", " ");
    	String regex = "(\\s+from\\s+)";
    	Pattern pattern = Pattern.compile(regex);
    	Matcher matcher = pattern.matcher(sql.toLowerCase());
    	while(matcher.find()){
    		fromSql = matcher.group(1);
    	}
    	int index = sql.toLowerCase().lastIndexOf(fromSql)+fromSql.length();
    	String subSql = sql.substring(index);
		return "select count("+countColumn+") as tmp_count from " + subSql;
    }

    private String getPageSql(String sql, Page page) {
        StringBuilder pageSql = new StringBuilder(200);
        if ("mysql".equals(dialect)) {
            pageSql.append(sql);
            pageSql.append(" limit " + page.getStartRow() + "," + page.getPageSize());
        } else if ("hsqldb".equals(dialect)) {
            pageSql.append(sql);
            pageSql.append(" LIMIT " + page.getPageSize() + " OFFSET " + page.getStartRow());
        } else if ("oracle".equals(dialect)) {
            pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
            pageSql.append(sql);
            pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
            pageSql.append(") where row_id > ").append(page.getStartRow());
        }
        return pageSql.toString();
    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_queryByPage", newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuffer keyProperties = new StringBuffer();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), int.class, EMPTY_RESULTMAPPING).build();
        resultMaps.add(resultMap);
        builder.resultMaps(resultMaps);
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    public void setProperties(Properties p) {
        dialect = p.getProperty("dialect");
        if (dialect == null || dialect.equals("")) {
            throw new RuntimeException("Mybatis分页异常,未设置数据库类型");
        }
        String offset = p.getProperty("offsetAsPageNum");
        if (offset != null && offset.toUpperCase().equals("TRUE")) {
            offsetAsPageNum = true;
        }
        String withcount = p.getProperty("rowBoundsWithCount");
        if (withcount != null && withcount.toUpperCase().equals("TRUE")) {
            rowBoundsWithCount = true;
        }
    }
    
    public static void main(String args[]){
    	String subSql = "";
    	String regex = "(.*\\s+from\\s+)";
    	Pattern pattern = Pattern.compile(regex);
    	Matcher matcher = pattern.matcher("select \n\t * from \n\t where id=5 \n\t and a.qq=5 or \n\t q=8");
    	if(matcher.find()){
    		subSql = matcher.group(1);
    	}
    	int index = "select \n\t * from \n\t where id=5 \n\t and a.qq=5 or \n\t q=8".indexOf(subSql)+subSql.length();
    	String result = "select \n\t * from \n\t where id=5 \n\t and a.qq=5 or \n\t q=8".substring(index);
		System.out.println(result);
    }
}